
#include <stdlib.h>
#include "list.h"

//this structure will help us implant list.
typedef struct Node_t{
	struct Node_t* next;
	struct Node_t* previous;
	ListElement data;
}*Node;

struct List_t{
	Node head;
	Node tail;
	Node current;
	int listSize;
	CopyListElement copy;
	FreeListElement freeElement;
};


//--help functions--//
//create node with given next previous address
static Node createNode(Node next, Node previous,List list,ListElement data){
	Node returnNode = (Node) malloc(sizeof(*returnNode));
	if (!returnNode){
		return NULL;
	}
	returnNode->next = next;
	returnNode->previous = previous;
	if(!list){
		returnNode->data = NULL;
	}
	else{
		returnNode->data = list->copy(data);
	}
	return returnNode;
}

//get the data in the node.
static ListElement getNodeElement(Node node){
	if(!node){
		return NULL;
	}
	return node->data;
}

//get the next node.
static ListElement getNextNode(Node node){
	if(!node){
		return NULL;
	}
	return node->next;
}


//since we get and set allot theese addresses, it's much easier to understand
//when dony by functions:

//get the list current adrees (point to node.)
static Node getListCurrentAddress(List list){
	if(!list){
		return NULL;
	}
	return list->current;
}

// set the current field in list to a given Node Address
// if list is NULL, then dont do anything(!);
static void setListCurrentAddress(List list,Node address){
	if(!list){
		return;
	}
	list->current = address;
	return;
}

//set the head address of a list
// if list is NULL, return NULL
static Node getListHeadAddress(List list){
	if(!list){
		return NULL;
	}
	return list->head;
}
//-----//


List listCreate(CopyListElement copyElement, FreeListElement freeElement){
	if(!copyElement || !freeElement){
		return NULL;
	};
	List returnList = (List) malloc(sizeof(struct List_t));
	if(!returnList){
		return NULL;
	}
	returnList->copy = copyElement;
	returnList->freeElement = freeElement;
	Node dummyTailNode = createNode(NULL,NULL,NULL,NULL);
	returnList->head = dummyTailNode;
	returnList->tail = dummyTailNode;
	returnList->listSize = 0;
	setListCurrentAddress(returnList,NULL);
	if(!returnList->head){
		listDestroy(returnList);
		return NULL;
	}
	return returnList;
}


List listCopy(List list){
	if(!list){
		return NULL;
	}
	List returnList = listCreate(list->copy,list->freeElement);
	if(!returnList){
		return NULL;
	}
	Node oldListCurrentNode = getListCurrentAddress(list);
	ListElement copyData = listGetFirst(list);
	for (int i = 0; i < list->listSize; ++i) {
		listInsertLast(returnList,copyData);
		if(oldListCurrentNode==getListCurrentAddress(list)){
			setListCurrentAddress(returnList,returnList->tail->previous);
		}
		copyData = listGetNext(list);
	}
	list->current = oldListCurrentNode;
	return returnList;
}


int listGetSize(List list){
	if(!list){
		return -1;
	}
	return list->listSize;
}


ListElement listGetFirst(List list){
	if(!list || (listGetSize(list)==0)){
		return NULL;
	}
	setListCurrentAddress(list,getListHeadAddress(list));
	return getNodeElement(getListCurrentAddress(list));
}


ListElement listGetNext(List list){
	if(!list || list->tail==getListHeadAddress(list) || !(list->current)){
		return NULL;
	}
	Node nextListNode = getNextNode(getListCurrentAddress(list));
	if(nextListNode==list->tail){
		list->current=NULL;
		return NULL;
	}
	list->current = nextListNode;
	return getNodeElement(nextListNode);
}


ListElement listGetCurrent(List list){
	if(!list || !getListCurrentAddress(list)){
		return NULL;
	}
	return(getNodeElement(getListCurrentAddress(list)));
}

ListResult listInsertFirst(List list, ListElement element){
	if(!list){
		return LIST_NULL_ARGUMENT;
	}
	Node nextNode = getListHeadAddress(list);
	Node insertNode = createNode(nextNode,NULL,list,element);
	if(!insertNode){
		return LIST_OUT_OF_MEMORY;
	}
	nextNode->previous=insertNode;
	list->head = insertNode;
	list->listSize++;
	return LIST_SUCCESS;

}

ListResult listInsertLast(List list, ListElement element){
	if(!list){
		return LIST_NULL_ARGUMENT;
	}
	if(listGetSize(list)==0){
		return listInsertFirst(list,element);
	}
	Node tailNode = list->tail;
	Node lastNode = tailNode->previous;
	Node insertNode = createNode(list->tail,lastNode,list,element);
	if(!insertNode){
		return LIST_OUT_OF_MEMORY;
	}
	lastNode->next=insertNode;
	tailNode->previous = insertNode;
	list->listSize++;
	return LIST_SUCCESS;

}

ListResult listInsertBeforeCurrent(List list, ListElement element){
	if(!list){
		return LIST_NULL_ARGUMENT;
	}
	if(!getListCurrentAddress(list)){
		return LIST_INVALID_CURRENT;
	}
	Node beforeNode = getListCurrentAddress(list)->previous;
	if(beforeNode==NULL){
		return listInsertFirst(list,element);
	}
	Node nextNode = getListCurrentAddress(list);
	Node insertNode = createNode(nextNode,beforeNode,list,element);
	if(!insertNode){
		return LIST_OUT_OF_MEMORY;
	}
	beforeNode->next = insertNode;
	nextNode->previous = insertNode;
	list->listSize++;
	return LIST_SUCCESS;
}

ListResult listInsertAfterCurrent(List list, ListElement element){
	if(!list){
		return LIST_NULL_ARGUMENT;
	}
	if(!getListCurrentAddress(list)){
		return LIST_INVALID_CURRENT;
	}
	Node nextNode = getListCurrentAddress(list)->next;
	if(nextNode == list->tail){
		return listInsertLast(list,element);
	}
	Node beforeNode = getListCurrentAddress(list);
	Node insertNode = createNode(nextNode,beforeNode,list,element);
	if(!insertNode){
		return LIST_OUT_OF_MEMORY;
	}
	beforeNode->next = insertNode;
	nextNode->previous = insertNode;
	list->listSize++;
	return LIST_SUCCESS;
}

ListResult listRemoveCurrent(List list){
	if(!list){
		return LIST_NULL_ARGUMENT;
	}
	Node nodeToDestroy = getListCurrentAddress(list);
	if(!nodeToDestroy){
		return LIST_INVALID_CURRENT;
	}
	list->freeElement(nodeToDestroy->data);
	Node previousNode = nodeToDestroy->previous;
	Node nextNode = nodeToDestroy->next;
	if(previousNode){
		previousNode->next = nextNode;
	}
	if(nextNode){
		nextNode->previous = previousNode;
	}
	if(nodeToDestroy==getListHeadAddress(list)){
		list->head = nextNode;
	}
	free(nodeToDestroy);
	list->current = NULL;
	list->listSize--;
	return LIST_SUCCESS;
}

//help function, swap the point address of 2 given ListElement addresses.
static void swapElementAddress(Node first,Node second){
	if(!first || !second){
		return;
	}
	ListElement temp = first->data;
	first->data = second->data;
	second->data = temp;
	return;
}

ListResult listSort(List list, CompareListElements compareElement){
	if(!list || !compareElement){
		return LIST_NULL_ARGUMENT;
	}
	ListElement listCurrent = listGetFirst(list);
	ListElement listNext = NULL;
	for (int i = 0; i < list->listSize; ++i) {
		for (int j = 1; j < list->listSize-i; ++j) {
			listNext = listGetNext(list);
			if(compareElement(listCurrent,listNext)>0){
				swapElementAddress(getListCurrentAddress(list)->previous,
						(getListCurrentAddress(list)));
			}
			else{
				listCurrent = listNext;
			}
		}
		listCurrent = listGetFirst(list);
	}
	return LIST_SUCCESS;
}

ListResult listClear(List list){
	if(!list){
		return LIST_NULL_ARGUMENT;
	}
	listGetFirst(list);
	Node nextNode = getNextNode(getListCurrentAddress(list));
	int listSize = listGetSize(list);
	for (int i = 0; i < listSize; ++i) {
		listRemoveCurrent(list);
		listGetNext(list);
		if(nextNode!=list->tail){
			setListCurrentAddress(list,nextNode);
			nextNode = getNextNode(nextNode);
		}
	}
	return LIST_SUCCESS;
}

List listFilter(List list, FilterListElement filterElement){
	if(!list || !filterElement){
		return NULL;
	}
	List returnList = listCopy(list);
	Node nextNode = NULL;
	ListElement listCurrent = listGetFirst(returnList);
	for (int i = 1; i <= listGetSize(list); ++i) {
		if(!(filterElement(listCurrent))){
			nextNode = getListCurrentAddress(returnList)->next;
			listRemoveCurrent(returnList);
			setListCurrentAddress(returnList,nextNode);
			listCurrent = listGetCurrent(returnList);
		}
		else{
			listCurrent = listGetNext(returnList);
		}
	}
	return returnList;
}


void listDestroy(List list){
	if(!list){
		return;
	}
	listClear(list);
	free(list->tail);
	free(list);
}

